#!/bin/sh
#
# APPR - Automated Profiling and Performance Regression - is an
# extensible performance profiling framework for OpenOffice.org.
#
# Copyright (C) 2005 Intel Corporation
#
# GNU Lesser General Public License Version 2.1
# ======================================================================
#
# This library is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2.1 of the
# License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
# USA
#
# ======================================================================
#
# APPR toolset main program

ACRONYM="APPR"
FULLNAME="Automated Profiling & Performance Regression"
VERSION="$Revision: 1.44 $"

# functions
DirName() {
	echo $1 | gawk '{match($0, /^(.*[/])[^/]+$/, opt); if (RLENGTH != -1) print opt[1]; else print "";}'
}

BaseName() {
	echo $1 | gawk '{match($0, /^.*[/]([^/]+)$/, opt); if (RLENGTH != -1) print opt[1]; else print $0;}'
}

PathName() {
	DIRNAME=$(DirName $1)
	DIRNAME=$(cd $DIRNAME; pwd)
	BASENAME=$(BaseName $1)
	if [ $DIRNAME = "/" ]; then
		echo "/"$BASENAME
	else
		echo $DIRNAME"/"$BASENAME
	fi
}

# Internal configuration
TARGET_CONFIG=appr.cfg
TARGET_OUTPUT=appr.output
TARGET_PROGRAM=/usr/lib/ooo-1.9/program/soffice.bin # Open Office (should be defined in PATH)
TARGET_PROCESS=Startup
REPORT_IDENTIFIER_BASE=OpenOffice.org
export REPORT_IDENTIFIER_BASE

MCOUNT=1
NCOUNT=1

APPR_DIR=$(cd $(DirName $0); pwd)
APPR_LOG_DIR=$APPR_DIR/logs
APPR_UTIL_DIR=$APPR_DIR/utils
export APPR_LOG_DIR APPR_UTIL_DIR

APPR_HOOK_PROG=$APPR_DIR/appr_hook
APPR_HOOK_ATTACH="$APPR_HOOK_PROG NCARE ATTACH"
export APPR_HOOK_PROG APPR_HOOK_ATTACH

OOMACRO_BASE=APPR.Actions
LOAD_PAUSE=$APPR_UTIL_DIR/pload
APPR_HOOK_DAEMON=$APPR_UTIL_DIR/appr_hook_daemon
APPR_HOOK_DAEMON_LOG_PREFIX=$APPR_LOG_DIR/$(BaseName $APPR_HOOK_DAEMON).log
APPR_HOOK_DAEMON_FOOTPRINT=/tmp/APPR_HOOK_DAEMON-$$
export LOAD_PAUSE APPR_HOOK_DAEMON_FOOTPRINT

OLD_LD_LIBRARY_PATH=$LD_LIBRARY_PATH

Usage() {
	echo "Usage: ${0##*/} [options] file..."
	echo
	echo "Options:"
	# echo -e "\t-c <CONFIGFILE>\t\tLoad CONFIGFILE before launch and profile the target usage."
	echo -e "\t-t <PROGRAM>\t\tUse PROGRAM as target program instead of the default one."
	echo -e "\t\t\t\tBy default, [$TARGET_PROGRAM] will be used."
	echo -e "\t-p <USAGE>\t\tLaunch and profile the target usage USAGE."
	echo -e "\t\t\t\tBy default, [$TARGET_USAGE] will be chosen."
	echo -e "\t\t\t\tFollowing target usages are supported:"
	echo -e "\t\t\t\t\tStartup"
	echo -e "\t\t\t\t\tLoadFile <DOCUMENT>[ <DOCUMENT> ...]"
	echo -e "\t\t\t\t\tPlaySlide <DOCUMENT>[ <DOCUMENT> ...]"
	echo -e "\t-m <MCOUNT>\t\tLaunch and profile the target usage for MCOUNT rounds."
	echo -e "\t-n <NCOUNT>\t\tRepeat execution within target usage (LoadFile, etc.) for NCOUNT rounds."
	# echo -e "\t-o <OUTPUT>"
	echo -e "\t-nx\t\t\tDonot acutllay run the command, just print them out."
	echo -e "\t-v, --version\t\tDisplay version information."
	echo -e "\t-h, --help\t\tDisplay this help screen."
	echo
	echo -e "\t--enable-ooo-rtl\tEnable hook agent embedded in target program (patched)."
	echo -e "\t--enable-ld-debug\tEnable glibc LD_DEBUG feature to collect library loading related information."
	echo -e "\t--enable-hooks=<HOOK>[,<HOOK>...]"
	echo -e "\t\t\t\tEnable the specified hook services HOOK. Following hook services are supported:"
	echo -e "\t\t\t\t\tvtune"
	echo -e "\t\t\t\t\tstrace"
	echo -e "\t\t\t\t\tmonitor"
	echo -e "\t\t\t\tExample: --enable-hooks=vtune,strace"
	echo -e "\t--start-paused\t\tPause profiling activity when launching the target program."
	echo -e "\t\t\t\tUser should be cautious to resume profiling activity some time later."
	echo -e "\t--tag"

# options for VTune session
	echo
	# echo -e "\tOptions for VTune hook service:"
}

Version() {
	echo "$ACRONYM ($FULLNAME)"
	echo "Version: $VERSION"
}

# Parse configuration file
if [ -r $APPR_DIR/$TARGET_CONFIG ]; then
	source $APPR_DIR/$TARGET_CONFIG
fi

# Make log directory
if [ ! -d $APPR_LOG_DIR ]; then
    mkdir $APPR_LOG_DIR
fi

# process arguments
if [ $# -eq 0 ]; then
	Usage
	exit 0
else
	while [ $# -ne 0 ]; do
		case $1 in
		-c)
			shift
			if [ -r $1 ]; then
				source $1
			else
				echo "Cannot find specified config file $1."
				exit -1
			fi
			;;
		-t)
			shift
			TARGET_PROGRAM=$1
			TARGET_PROGRAM_DIR=$(cd $(DirName $TARGET_PROGRAM); pwd)
			export LD_LIBRARY_PATH=$TARGET_PROGRAM_DIR:$LD_LIBRARY_PATH
			;;
		-p)
			shift
			TARGET_PROCESS=$1
			;;
		-m)
			shift
			MCOUNT=$(($1))
			if [ ${#MCOUNT} -eq 0 ] || [ $MCOUNT -lt 1 ] || [ $MCOUNT -gt 10 ]; then
				echo "The major count should be within the range from 1 to 10."
				exit -1
			fi
			;;
		-n)
			shift
			NCOUNT=$(($1))
			if [ ${#NCOUNT} -eq 0 ] || [ $NCOUNT -lt 1 ] || [ $NCOUNT -gt 10 ]; then
				echo "The minor count should be within the range from 1 to 10."
				exit -1
			fi
			;;
#		-o)
#			shift
#			TARGET_OUTPUT=$1
#			;;
		-nx)
			export EXEC="echo"
			;;
		--enable-ooo-rtl)
			export APPR_HOOK_ENABLE_OOO_RTL=TRUE
			export RTL_LOGFILE=$APPR_LOG_DIR/RTL_LOG
			export APPR_HOOK_ENABLE="$APPR_HOOK_ENABLE ${1##*enable-}"
			;;
		--enable-ld-debug)
			export APPR_HOOK_ENABLE_LDDEBUG=TRUE
			export APPR_HOOK_ENABLE="$APPR_HOOK_ENABLE ${1##*enable-}"
			;;
		--enable-hooks=*)
			APPR_HOOK_ENABLE_TEMP=${1##*=}
			APPR_HOOK_ENABLE_TEMP=${APPR_HOOK_ENABLE_TEMP//,/ }

			# set enable environment variables
			for hook in $APPR_HOOK_ENABLE_TEMP; do
				HOOKNAME=$(echo $hook | tr [:lower:] [:upper:])
				eval export APPR_HOOK_ENABLE_$HOOKNAME=true
			done

			export APPR_HOOK_ENABLE="$APPR_HOOK_ENABLE $APPR_HOOK_ENABLE_TEMP"

			# VTune enabled
			if [ ${#APPR_HOOK_ENABLE_VTUNE} -ne 0 ]; then
				export VTUNE_USER_DIR=$APPR_DIR/vtune
				export APPR_HOOK_SESSION_MANAGER=VTUNE

				# configure library path
				VTUNEDIR=$(echo $PATH | sed "s/:/\n/g;" | sed "s/vtune.*$/vtune/" | grep -i vtune | sort -u)
				VTUNEDIR=$(find $VTUNEDIR -type d | grep bin$ | sort -u)

				for dir in $VTUNEDIR; do
					export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$dir
				done
			fi

			# Callgrind enabled
			if [ ${#APPR_HOOK_ENABLE_CALLGRIND} -ne 0 ]; then
				export APPR_HOOK_SESSION_MANAGER=CALLGRIND
			fi

			;;
		--start-paused)
			export APPR_HOOK_START_PAUSED=TRUE
			;;
		--tag)
			shift;
			export REPORT_IDENTIFIER_BASE=$1
			;;
		-v|--version)
			Version
			exit 0
			;;
		-h|--help)
			Usage
			exit 0
			;;
		*)
			if [ -f $1 ] && [ -r $1 ]; then
				TARGET_DOCUMENT="file://$(PathName $1 2>/dev/null)"
				TARGET_DOCUMENTS=$TARGET_DOCUMENTS${TARGET_DOCUMENTS:+ }$TARGET_DOCUMENT
			fi
			;;
		esac
		shift
	done
fi

function runonce()
# $1: Round
# $2: Process (ooo macro)
{
	TIMESTAMP=$(date +%H%M%S)
	export ROUND TIMESTAMP

	# "BEGIN" hook
	export CMDFILE=/tmp/APPR_HOOK_CMDFILE
	$APPR_HOOK_PROG "NCARE" "BEGIN"

	# set environment varialbes to enable ld debug
	if [ ${#APPR_HOOK_ENABLE_LDDEBUG} -ne 0 ]; then
		export LD_DEBUG=statistics
		export LD_DEBUG_OUTPUT=$APPR_LOG_DIR/ld_output
	fi

	# load target program (ooo) and wait for signal to continue
	if [ ${#APPR_HOOK_SESSION_MANAGER} -eq 0 ]; then
		# no session manager specified
        	$EXEC $LOAD_PAUSE $TARGET_PROGRAM $TARGET_PROGRAM_OPTIONS &
		export APPR_HOOK_TARGET_PID=$!
	else
		if [ -r $CMDFILE ] && RUNCMD=$(< $CMDFILE) && [ ${#RUNCMD} -ne 0 ]; then
			$EXEC $RUNCMD &
			SESSIONPID=$!
		else
			echo "Failed in executing command of session manager [$APPR_HOOK_SESSION_MANAGER]."
			exit -1
		fi

		# remove temporary file for transferring command
		rm -f $CMDFILE

		while [ ${#APPR_HOOK_TARGET_PID} -eq 0 ]; do
			[ ${#EXEC} -ne 0 ] && [ $EXEC = "echo" ] && break

			sleep 1
			APPR_HOOK_TARGET_PID=$(pgrep -n $(BaseName $LOAD_PAUSE))
		done

		export APPR_HOOK_TARGET_PID
	fi

	# start APPR hook daemon
	APPR_HOOK_DAEMON_LOGFILE=$APPR_HOOK_DAEMON_LOG_PREFIX-$RUNTAG-$ROUND-$APPR_HOOK_TARGET_PID
	$EXEC $APPR_HOOK_DAEMON >&$APPR_HOOK_DAEMON_LOGFILE &
	export APPR_HOOK_DAEMON_PID=$!

	# mark "ATTACH" hook
	# moved into LOAD_PAUSE utility

	# send signal to target program (ooo) and wait until it completes
	$EXEC kill -CONT $APPR_HOOK_TARGET_PID

	# wait until the target program exits
	if [ ${#SESSIONPID} -ne 0 ]; then
		while ps -p $APPR_HOOK_TARGET_PID >&/dev/null; do
			sleep 5;
		done

		$APPR_HOOK_PROG "NCARE" "EXIT"

		$EXEC wait $SESSIONPID
	else
		$EXEC wait $APPR_HOOK_TARGET_PID
	fi

	# send signal to appr hook daemon to kill it
	$EXEC kill $APPR_HOOK_DAEMON_PID
	rm -fr $APPR_HOOK_DAEMON_FOOTPRINT

	# "END" hook
	$APPR_HOOK_PROG "NCARE" "END"

	if [ ${#APPR_HOOK_ENABLE_LDDEBUG} -ne 0 ]; then
		unset LD_DEBUG
		unset LD_DEBUG_OUTPUT
		$EXEC mv $APPR_LOG_DIR/ld_output.$APPR_HOOK_TARGET_PID $APPR_LOG_DIR/ld-debug.log-$RUNTAG-$ROUND-$APPR_HOOK_TARGET_PID
		$EXEC find $APPR_LOG_DIR -name ld_output.* | xargs rm -f
	fi

	if [ ${#APPR_HOOK_ENABLE_OOO_RTL} -ne 0 ]; then
		$EXEC mv $APPR_LOG_DIR/RTL_LOG_${APPR_HOOK_TARGET_PID}.log $APPR_LOG_DIR/ooo-rtl.log-$RUNTAG-$ROUND-$APPR_HOOK_TARGET_PID
	fi

	# invoke report generator to generate final result for this round
	for hook in $APPR_HOOK_ENABLE; do
		HOOKOPTIONS="--$hook=$APPR_LOG_DIR/${hook}.log-$RUNTAG-$ROUND-$APPR_HOOK_TARGET_PID"
		APPR_REPORT_OPTIONS="$APPR_REPORT_OPTIONS $HOOKOPTIONS"
	done

	export REPORT_IDENTIFIER="$REPORT_IDENTIFIER_BASE (round $ROUND)"
	REPORT_XML_FILE=$APPR_LOG_DIR/appr-report.xml-$RUNTAG-$ROUND-$APPR_HOOK_TARGET_PID
	REPORT_SVG_FILE=$APPR_LOG_DIR/appr-report.svg-$RUNTAG-$ROUND-$APPR_HOOK_TARGET_PID
	$EXEC ./report_generator $APPR_REPORT_OPTIONS --outfile=$REPORT_XML_FILE 2>/dev/null
	$EXEC ./draw_graph $REPORT_FILE --report=$REPORT_XML_FILE --output=$REPORT_SVG_FILE 2>/dev/null
	$EXEC firefox $REPORT_SVG_FILE
}

function apprclear()
{
	echo "Interrupt signal trapped, will exit ..."

	# kill load-pause program
	PROCNAME=$(BaseName $LOAD_PAUSE)
	PROCNAME=${PROCNAME:0:15}
	$EXEC pkill -KILL $PROCNAME

	# kill target program
	PROCNAME=$(BaseName $TARGET_PROGRAM)
	PROCNAME=${PROCNAME:0:15}
	$EXEC pkill -KILL $PROCNAME

	# kill appr_hook daemon process
	PROCNAME=$(BaseName $APPR_HOOK_DAEMON)
	PROCNAME=${PROCNAME:0:15}
	$EXEC pkill -KILL $PROCNAME

	# "KILL" hook
	$APPR_HOOK_PROG "NCARE" "KILL"

	# remove temporary ld debug files
	$EXEC find $APPR_LOG_DIR -name ld_output.* | xargs rm -f

	# exit
	exit -1
}

# build necessary utilities if it does not present
if [ ! -d $APPR_UTIL_DIR ]; then
	$EXEC ./appr_make
fi

# trap the Ctrl-C and Kill signal
trap apprclear SIGKILL SIGSTOP SIGINT # EXIT

# invoke target program (automated OpenOffice with starBASIC)
case $TARGET_PROCESS in
Startup)
	TARGET_PROGRAM_OPTIONS="macro:///$OOMACRO_BASE.$TARGET_PROCESS()"
	export TARGET_PROGRAM TARGET_PROGRAM_OPTIONS

	# "INIT" hook: initialize for consequent usage(s) exection
	echo -e "\n[$TARGET_PROCESS]"
	export RUNTAG=$TARGET_PROCESS
	$APPR_HOOK_PROG "NCARE" "INIT"

	for (( i = 1; $i <= $MCOUNT; i++ )); do
		export ROUND=$i
		echo "Round $ROUND"
		runonce $i &
		wait
	done

	# "DONE" hook: post process for system clean, etc.
	$APPR_HOOK_PROG "NCARE" "DONE"
	;;
LoadFile)
	if [ ${#TARGET_DOCUMENTS} -eq 0 ]; then
		echo "No documents specified."
		exit
	fi

	for document in $TARGET_DOCUMENTS; do
		TARGET_PROGRAM_OPTIONS="macro:///$OOMACRO_BASE.$TARGET_PROCESS($document,$NCOUNT)"
		export TARGET_PROGRAM TARGET_PROGRAM_OPTIONS

		# "INIT" hook: initialize for consequent usage(s) exection
		echo -e "\n[$TARGET_PROCESS $document]"
		export RUNTAG=$TARGET_PROCESS:${document##*/}
		$APPR_HOOK_PROG "NCARE" "INIT"

		for (( i = 1; $i <= $MCOUNT; i++ )); do
			export ROUND=$i
			echo "Round $ROUND"
			runonce $i &
			wait
		done

		# "DONE" hook: post process for system clean, etc.
		$APPR_HOOK_PROG "NCARE" "DONE"
	done
	;;
PlaySlide)
	if [ ${#TARGET_DOCUMENTS} -eq 0 ]; then
		echo "No documents specified."
		exit
	fi

	for document in $TARGET_DOCUMENTS; do
		TARGET_PROGRAM_OPTIONS="macro:///$OOMACRO_BASE.$TARGET_PROCESS($document,$NCOUNT)"
		export TARGET_PROGRAM TARGET_PROGRAM_OPTIONS

		# "INIT" hook: initialize for consequent usage(s) exection
		echo -e "\n[$TARGET_PROCESS $document]"
		export RUNTAG=$TARGET_PROCESS:${document##*/}
		$APPR_HOOK_PROG "NCARE" "INIT"

		for (( i = 1; $i <= $MCOUNT; i++ )); do
			export ROUND=$i
			echo "Round $ROUND"
			runonce $i &
			wait
		done

		# "DONE" hook: post process for system clean, etc.
		$APPR_HOOK_PROG "NCARE" "DONE"
	done
	;;
*)
	export TARGET_PROGRAM

	# "INIT" hook: initialize for consequent usage(s) exection
	echo -e "\n[NULL]"
	export RUNTAG="NULL"
	$APPR_HOOK_PROG "NCARE" "INIT"

	for (( i = 1; $i <= $MCOUNT; i++ )); do
		export ROUND=$i
		echo "Round $ROUND"
		runonce $i &
		wait
	done

	# "DONE" hook: post process for system clean, etc.
	$APPR_HOOK_PROG "NCARE" "DONE"
esac

export LD_LIBRARY_PATH=$OLD_LD_LIBRARY_PATH
echo "APPR end."

